package com.yinxing.webapi.config;

import com.yinxing.framework.aspectj.AspectOrder;
import com.yinxing.framework.domain.R;
import com.yinxing.framework.shiro.exception.RequiresAuthenticationException;
import com.yinxing.framework.shiro.exception.RequiresPermissionException;
import com.yinxing.framework.utils.IpUtils;
import com.yinxing.framework.utils.JsonUtils;
import com.yinxing.framework.utils.ServletUtils;
import com.yinxing.webapi.code.entity.sys.SysLog;
import com.yinxing.webapi.code.entity.sys.SysPermission;
import com.yinxing.webapi.code.service.sys.ISysLogService;
import com.yinxing.webapi.code.service.sys.ISysPermissionService;
import com.yinxing.webapi.shiro.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;

@Slf4j
@Aspect
@Component
@Order(AspectOrder.ShiroAspect)
public class ShiroAopConfig {

    @Autowired
    private ISysPermissionService sysPermissionService;

    @Autowired
    private ISysLogService sysLogService;

    @Around("execution(public * com.yinxing.webapi.code.controller..*.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        long startTime;
        long runTime = 0;
        Throwable throwable = null;

        //Controller类名
        String className = point.getTarget().getClass().getSimpleName();
        //方法名
        MethodSignature ms = (MethodSignature) point.getSignature();
        String methodName = ms.getMethod().getName();

        //构建请求路径 例如:/SysTableColumnController/update
        String urlPattern = "/" + className + "/" + methodName;

        //缓存中获取权限信息 如果不存在缓存会去数据库中加载
        SysPermission permission = sysPermissionService.getSysPermissionByCache(urlPattern);

        //登录检查
        if (permission.getCheckLogin_() != null && permission.getCheckLogin_()) {
            if (!getSubject().isAuthenticated()) {
                throw new RequiresAuthenticationException("用户未登录");
            }
        }

        //检查权限(admin具有最高权限 可以访问任何资源)
        if (permission.getCheckPermission_() != null && permission.getCheckPermission_()) {
            if (!getSubject().hasRole("admin") && !getSubject().isPermitted(permission.getUrlPattern_())) {
                throw new RequiresPermissionException("用户不具备所需权限" + permission.getUrlPattern_());
            }
        }

        try {
            //调用目标方法
            startTime = System.currentTimeMillis();
            result = point.proceed();
            runTime = System.currentTimeMillis() - startTime;
        } catch (Throwable e) {
            throwable = e;
            throw e;
        } finally {
            //记录日志
            if(permission.getRequireLog_() != null && permission.getRequireLog_()) {
                handleLog(permission, point, runTime, result, throwable);
            }
        }

        return result;
    }

    private void handleLog(SysPermission permission, ProceedingJoinPoint point, long runTime, Object returnValue, Throwable e) {

        SysLog sysLog = new SysLog();

        //记录日志时间
        sysLog.setLogTime_(LocalDateTime.now());

        MethodSignature ms = (MethodSignature) point.getSignature();

        //请求来源IP地址
        String requestHost = IpUtils.getIpAddr(ServletUtils.getRequest());
        sysLog.setRequestHost_(requestHost);

        //请求路径
        String requestUrl = ServletUtils.getRequest().getRequestURI();
        sysLog.setRequestUrl_(requestUrl);

        //请求方式
        String requestMethod = ServletUtils.getRequest().getMethod();
        sysLog.setRequestMethod_(requestMethod);

        //操作名称
        sysLog.setActionName_(permission.getActionName_());

        //执行时间.毫秒
        sysLog.setRunTime_((int) runTime);

        //记录异常信息
        if(e != null) {
            sysLog.setException_(true);
            sysLog.setExceptionClass_(e.getClass().getSimpleName());
        } else {
            sysLog.setException_(false);
            sysLog.setExceptionClass_("无");
        }

        //如果账户已经登录 则记录账户信息
        if(getSubject().isAuthenticated()) {
            //登录账户ID
            sysLog.setUserId_(LoginUser.current().getUserId());
            //登录账户名称
            sysLog.setUserName_(LoginUser.current().getUsername());
        }

        //记录请求参数
        if(permission.getLogRequestParam_() != null && permission.getLogRequestParam_()) {
            Map<String, String> requestMap = new LinkedHashMap<>();
            for(Map.Entry<String, String[]> entry : ServletUtils.getRequest().getParameterMap().entrySet()) {
                requestMap.put(entry.getKey(), StringUtils.arrayToCommaDelimitedString(entry.getValue()));
            }
            sysLog.setRequestParams_(JsonUtils.toJsonMissError(requestMap));
        }

        //记录返回值
        if(permission.getLogReturnValue_() != null && permission.getLogReturnValue_() && e == null) {
            if(returnValue != null && returnValue instanceof R) {
                R r = (R) returnValue;
                sysLog.setReturnValue_(JsonUtils.toJsonMissError(r.getData()));
            } else {
                sysLog.setReturnValue_(JsonUtils.toJsonMissError(returnValue));
            }
        }

        //异步插入
        sysLogService.asyncInsert(sysLog);
    }

    private Subject getSubject() {
        return SecurityUtils.getSubject();
    }
}
